使用preStop Hook实现ALB Ingress后端Pod滚动升级时平滑下线Pod

您可以配置preStop Hook,实现ALB Ingress后端Pod的平滑下线,使Pod在被ALB Ingress Controller完全从后端服务组中移除后才正式下线。配置后,容器在滚动更新期间能够保障服务的流量无缝切换,避免出现中断。

前提条件

原理介绍

Pod生命周期

Pod中的容器包括两种类型:

  • 初始化容器(Init容器):运行服务的主容器前置依赖的容器,完成一些初始化操作。

  • 主容器(工作容器):运行服务进程的容器。

Pod启动过程如下:

image
  1. 运行初始化容器:在主容器启动之前,要运行的容器,为主容器执行预置操作。

  2. 运行主容器:

    1. 容器启动后执行postStart Hook函数。

    2. 容器执行存活性探测(Liveness Probe)、就绪性检测(Readiness Probe)。

    3. 在容器退出前执行preStop Hook函数。

Pod终止流程和网络规则更新流程

在删除Pod时K8s会同时进行Pod终止和网络规则更新两个主要的流程。Pod终止流程和网络更新流程如下。

Pod终止流程

  1. kube-apiserver收到Pod删除请求,将Pod标记为Terminating状态。

  2. 如果Pod定义了preStop Hook,将执行preStop Hook。

  3. K8s集群向容器发送SIGTERM信号。

  4. 等待容器停止,或等待Pod删除宽限期超时。

    说明

    Pod中容器删除宽限期terminationGracePeriodSeconds默认为30秒。

  5. 超时Pod删除宽限期后容器仍未终止,K8s发送SIGKILL信号给容器。

  6. Pod被完全删除。

网络规则更新流程

  1. kube-apiserver收到Pod删除请求,将Pod标记为Terminating状态。

  2. Endpoint Controller从Endpoint对象中删除Pod的IP。

  3. ALB Ingress Controller进行Server节点调谐,将Service Endpoints从后端服务器组中移除,不再将流量路由到被删除的Pod。

使用preStop Hook来实现Pod平滑下线

当Pod滚动升级过程中,如果旧的Pod没有平滑退出,将导致应用无法正常处理业务请求,出现如下错误状态码,影响应用的可用性。

状态码

问题原因

504

当旧Pod在未处理完正常请求情况下被删除,并且该请求为非幂等。

502

当ACK集群已经删除旧Pod,容器收到终止信号(SIGTERM)并迅速终止,但ALB Ingress Controller仍在调谐流程中,导致Pod未及时从后端服务器组中移除,流量依然被错误地路由到已终止的旧Pod上。

为了避免以上的情况发生,您可以采取如下方案。

kube-apiserver接收到Pod删除请求时,通过在Deployment配置内添加preStop Hook,可以为容器设置一个"Sleep"暂停期。这个暂停期是让容器有时间在收到SIGTERM之前完成网络规则的更新,并等待ALB Ingress Controller完成Server调谐事件并确保Pod已从后端服务器组中移除。这一步骤对于保障Pod的平稳下线至关重要,避免在滚动更新或服务重启期间造成流量中断。

同时,Kubernetes为容器设定了在接收到SIGTERM后能持续运行的最大宽限期(terminationGracePeriodSeconds)为30秒。当程序关闭时间与preStop Hook指定的操作时间之和超过了30秒时,默认宽限期将不足以让容器完成所有的关闭步骤。kubelet会在等待2秒后直接给容器发送立即终止(SIGKILL)信号,导致容器强制退出。

说明

如果程序的关闭时间和在Deployment配置的preStop Hook之和超过30秒,应将terminationGracePeriodSeconds重新设置,调整为大于30秒,确保容器优雅退出。

步骤一:并配置Pod平滑退出preStop Hook

  1. 创建并保存tea-service.yaml,配置确保Pod平滑退出的preStop Hook。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: tea
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: tea
      template:
        metadata:
          labels:
            app: tea
        spec:
          containers:
          - name: tea
            image: registry.cn-hangzhou.aliyuncs.com/acs-sample/nginxdemos:latest
            ports:
            - containerPort: 80
            lifecycle:  # 设置preStop Hook函数,使kube-apiserver等待10秒后再向Pod发送SIGTERM信号。
              preStop:  # preStop钩子设置。
                exec:  # 通过执行命令的方式来实现preStop操作。
                  command:  # 定义要执行的命令。
                  - /bin/sh
                  - -c
                  - "sleep 10"  # 执行sleep操作,暂停10秒。
          terminationGracePeriodSeconds: 45  # 设置Pod删除宽限期。
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: tea-svc
    spec:
      ports:
      - port: 80
        targetPort: 80
        protocol: TCP
      selector:
        app: tea
      type: NodePort
  2. 执行以下命令,部署示例Deployment和Service。

    kubectl apply -f tea-service.yaml
  3. 创建并保存tea-ingress.yaml文件,用于配置Ingress。

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: tea-ingress
    spec:
      ingressClassName: alb
      rules:
       - host: demo.ingress.top
         http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: tea-svc
                port:
                  number: 80
  4. 执行以下命令,部署Ingress。

    kubectl apply -f tea-ingress.yaml
  5. 执行以下命令,获取ALB Ingress的ADDRESS

    kubectl get ingress

    预期输出:

    NAME                    CLASS   HOSTS                     ADDRESS                                              PORTS   AGE
    tea-ingress             alb     demo.ingress.top          alb-110zvs5nhsvfv*****.cn-chengdu.alb.aliyuncs.com   80      7m5s

步骤二:结果验证

  1. 启动测试脚本。

    1. 使用以下内容,创建test.sh测试脚本文件。此脚本用于测试应用的可用性,按每秒一次的频率请求Nginx服务并查看状态码。

      #!/bin/bash
      HOST="demo.ingress.top"
      DNS="alb-110zvs5nhsvfv*****.cn-chengdu.alb.aliyuncs.com"   # 填写ALB Ingress的ADDRESS值。
      printf "Response Code|| TIME \n" >> log.txt
      
      while true; do
        RESPONSE=$(curl -H Host:$HOST  -s -o /dev/null -w "%{http_code}" -m 1  http://$DNS/)
        TIMESTAMP=$(date +%Y-%m-%d_%H:%M:%S)
        echo "$TIMESTAMP - $RESPONSE" >> log.txt
        sleep 1
      done
    2. 执行以下命令运行测试脚本test.sh

      bash test.sh
  2. 执行以下命令,重新部署应用,触发Pod滚动升级。

    kubectl rollout restart deploy tea 
  3. 验证Pod滚动升级。

    1. 初始阶段应用副本数为3。

      image

    2. 在重新部署应用时,新Pod创建阶段由于新Pod没有就绪,旧Pod仍在提供服务,会同时运行新旧两个Pod。

      image

    3. 新Pod就绪且成功挂载至ALB后端服务器组后,会终止旧Pod。

      image

    4. 所有旧Pod完成preStop Hook函数或超出terminationGracePeriodSeconds超时时间后,kubectl会向Container发送信号终止旧的Pod,此时滚动更新完成。

      image

  4. 查看测试脚本执行结果。检查滚动更新过程中请求的状态码,可以看到状态码全部为200,更新过程没有中断。

    执行如下命令,查看测试脚本执行结果。

    cat log.txt

    预期输出:

    image

相关文档